4. 命令行

Grails的命令行系统是构建于 Gant 之上,Gant就是使用Groovy对 Apache Ant 进行了简单的包装。

然而,Grails通过约定规则以及grails命令的使用带来了一些改进。当你键入如下内容时:

grails [命令名称]
为了Gant脚本的执行,Grails会在下列目录中做一次搜索:

Grails将把小写的命令名称(如run-app)转换为单词连写的格式。因此如果键入的是

grails run-app

,那么Grails将会搜索下列文件:

如果找到多个同名的文件,Grails将要求你选择执行其中的一个。当Grails执行一个Gant脚本的时候,它会首先调用定义在脚本文件中的“default”任务。如果找不到“default”任务,Grails将退出并报错。

获得可用的命令及其帮助信息:

grails help

这个命令将输出Grails当前所知的命令列表和使用说明:

Usage (optionals marked with *): 
grails [environment]* [target] [arguments]*

Examples: grails dev run-app grails create-app books

Available Targets (type grails help 'target-name' for more info): grails bootstrap grails bug-report grails clean grails compile ...

参考本使用指南左侧菜单中的命令行指南,可以获得更多的命令行的信息。

4.1 创建Gant脚本

你可以在项目的根目录下运行 create-script 命令来创建你自己的Gant脚本。例如如下命令:

grails create-script compile-sources

这将创建一个叫做 scripts/CompileSources.groovy 的脚本。Gant脚本本身与规范的Groovy脚本非常相似,除了它支持“targets”的概念以及它们之间的依赖关系:

target(default:"default任务是由Grails来执行的") {
        depends(clean, compile)
}
target(clean:"清除一些东西") {
        ant.delete(dir:"output")
}
target(compile:"编译一些源码") {
        ant.mkdir(dir:"mkdir")
        ant.javac(srcdir:"src/java", destdir:"output")
}

如上面的脚本所说明的,这个内置的 ant 变量可以访问 Apache Ant API

在以前的Grails中(1.0.3和以下),这个变量是 Ant,即第一个字母是大写的。

你也可以依赖其他的任务,只要在 default 任务中使用 depends 方法说明。

默认任务(default)

在上边的例子中,我们使用明确的名称“default”来指明一个任务。这是为一个脚本文件定义默认任务的一种方式。可选的另一种方式是使用 setDefaultTarget() 方法:

target("clean-compile": "对应用程序源文件执行清理并编译。") {
        depends(clean, compile)
}
target(clean:"清除文件") {
        ant.delete(dir:"output")
}
target(compile:"编译源码") {
        ant.mkdir(dir:"mkdir")
        ant.javac(srcdir:"src/java", destdir:"output")
}

setDefaultTarget("clean-compile")

这样将允许你从其他脚本中直接调用默认的任务。另外,尽管在这个例子中我们把调用 setDefaultTarget() 这一行放在了脚本文件的最后,但你可以把它放在任何位置,只要它位于它要引用的那个任务 之后(在这个例子中这个任务就是“clean-compile”)。

哪种方式更好?坦率地说,你可以使用你喜欢的那种方式——看起来这两种方式都没有什么突出的优势。我们应该讨论的一个问题是,如果你想要允许任何其他脚本都能调用你的“default”任务,那么你应该把它移动到一个没有默认任务的共享脚本文件中。关于这些内容,我们将在下一章节进行更多讨论。

4.2 重用Grails脚本

Grails带了许多开箱即用的命令行功能,你会发现这在你自己的脚本中那个会很有用(查看参考指南的命令行指南部分可以获得所有命令的详细信息)。尤其是使用 compilepackagebootstrap 脚本。

下边的bootstrap脚本例子允许你启动一个Spring的 ApplicationContext 实例,通过它来访问数据源等(集成测试时可以这样用):

includeTargets << grailsScript("_GrailsBootstrap")

target ('default': "Load the Grails interactive shell") { depends( configureProxy, packageApp, classpath, loadApp, configureApp )

Connection c try { // 使用连接一些事情 c = appCtx.getBean('dataSource').getConnection() } finally { c?.close() } }

从其他脚本文件引入任务

Gant允许你从另一个Gant脚本文件中引入所有任务(除了“default”)。然后你就可以依赖或调用这些已经被定义在当前脚本文件中的任务了。实现的途径是 includeTargets 属性。使用左移操作符来简单的“附加”一个文件或类:

includeTargets << new File("/path/to/my/script.groovy")
includeTargets << gant.tools.Ivy
不用太担心关于使用一个类的语法,它是相当专业的。要是你感兴趣,可以看看Gant的文档。

核心的Grails任务

如你在本章开头部分所看到的例子,当使用includeTargets来包含核心的Grails任务时,既没有使用基于文件的语法也没有使用基于类的语法。取而代之的,你应该使用Grails命令启动器提供的特殊的 grailsScript() 方法(注意这个方法在一般的Gant脚本中是不可用的,只有在Grails环境中才行)。

grailsScript() 方法的语法是非常简单易读的:简单的把你想要包含的Grails脚本文件的名称传入,不需要任何路径信息。以下是一个你可能想要重用的Grails脚本列表:
脚本 描述
_GrailsSettings 你确实应该包括这个!幸运的是,它已经被所有其他Grails脚本文件自动包括了(_GrailsProxy),因此你通常不必明确的包括它。
_GrailsEvents 如果你想要触发事件,你应该包括这个。添加一个 event(String eventName, List args) 方法。另外,这也被几乎所有其他Grails脚本文件包括。
_GrailsClasspath 安装编译、测试和运行用的classpath。如果你想使用它们,就包含这个脚本。另外,这也由几乎所有其他Grails脚本包含。
_GrailsProxy 如果你需要访问互联网,为了避免遇到代理引起的问题请包含这个脚本。
_GrailsArgParsing 提供一个 parseArguments 任务,就像字面上的意思:当运行你的脚本的时候解析用户提供的参数。把参数添加到 argsMap 属性中。
_GrailsTest 包含所有共享的测试代码。如果你要添加额外的测试这将非常有用。
_GrailsRun 为你提供在配置好的servlet容器中运行应用程序时需要的一切,可以是正常的运行(runApp/runAppHttps),也可以是来自于一个WAR文件(runWar/runWarHttps)。

这些由Grails提供的脚本很值得对它们进行深入的分析,从而找出哪些类型的任务是可以使用的。任何脚本文件都是以“_”作为前缀以便进行重用。

在Grails 1.1版本之前,“_Grails...”这样的脚本文件是不可用的。而通常会包含对应命令脚本,例如“Init.groovy”或“Bootstrap.groovy”。

同样,在Grails 1.0.4版本之前,是无法使用 grailsScript() 方法的,你只能使用 includeTargets << new File(...) 并指明脚本的完整位置。(例如: $GRAILS_HOME/scripts)。

脚本结构

你可能对这些下划线词语作为Grails脚本的名称感到疑惑。用_internal_作为一个脚本或者用没有对应的“command”的其他单词,这些就是Grails的决定方式。因此无法运行例如“grails _grails-settings”这样的命令。这也就是为什么它们没有个默认的任务。

内部脚本是和代码共享重用相关的。实际上,我们建议在自己的脚本中使用类似的方式:把你的所有任务放入一个内部脚本中可以更容易的共享,然后提供简单的命令脚本来解析任何命令行参数并委托给内部脚本中的任务。假如你有一个脚本要运行一些功能测试——你可以将它们像这样分离:

./scripts/FunctionalTests.groovy:

includeTargets << new File("${basedir}/scripts/_FunctionalTests.groovy")

target(default: " 这个 项目运行功能测试。") { depends(runFunctionalTests) }

./scripts/_FunctionalTests.groovy:

includeTargets << grailsScript("_GrailsTest")

target(runFunctionalTests: "运行功能测试。") { depends(...) … }

以下是在编写脚本时常用的一些指导方案:

4.3 钩子事件

Grails提供了钩住脚本事件的能力。这里指的是当Grails的任务和插件脚本执行的时候能触发的一些事件。

这个机制是故意简单化和松散的规定的。可能的事件列表是不会以任何方式固定的,所以可以钩住那些被插件脚本触发的事件,在核心目标脚本中没有类似的事件。

定义事件处理器

事件处理器是定义在称为 _Events.groovy 的脚本文件中。Grails会在以下位置搜索这些脚本:

无论事件在何时被激发, 所有 已经注册到该事件的处理器都会被执行。需要注意的是处理器的注册工作会由Grails自动进行,你只需要在相关的 _Events.groovy 文件中声明即可。

在Grails 1.0.4版本之前,脚本文件被命名为 Events.groovy,它没有前下划线。

事件处理器是分块定义在 _Events.groovy 文件中,使用“event”作为名称的开头部分。下边的例子可以被放在你的 /scripts 目录中来展示这个特性:

eventCreatedArtefact = { type, name ->
   println "Created $type $name"
}

eventStatusUpdate = { msg -> println msg }

eventStatusFinal = { msg -> println msg }

你可以看到这儿有三个处理器分别是:eventCreatedArtefacteventStatusUpdateeventStatusFinal。Grails提供了一些标准的事件,它们在命令行参考指南中有描述。例如compile命令会激发下列事件:

触发事件

要简单地触发一个包含Init.groovy脚本的事件并调用event()闭包:

includeTargets << grailsScript("_GrailsEvents")

event("StatusFinal", ["Super duper plugin action complete!"])

公共事件

下表是一些可以被利用的公共事件:

事件 参数 描述
StatusUpdate message 传入一个标志当前脚本状态或进展的字符串
StatusError message 传入一个标志来自当前脚本的错误信息的字符串
StatusFinal message 传入一个标志最终脚本状态消息的字符串,例如:当编译一个任务时,即使任务还没有退出脚本环境
CreatedArtefact artefactType,artefactName 当一个 create-xxxx 脚本已执行完成并创建了一个工件时调用
CreatedFile fileName 当一个项目的源码文件被创建时调用,但不包括那些由Grails管理的固定文件
Exiting returnCode 当脚本环境即将正常的退出时调用
PluginInstalled pluginName 在一个插件被安装之后调用
CompileStart kind 当编译过程开始时调用,针对这几种类型的编译——源文件和测试文件
CompileEnd kind 当编译过程完成时调用,针对这几种类型的编译——源文件和测试文件
DocStart kind 当生成文档过程即将开始时调用——生成javadoc或groovydoc时
DocEnd kind 当生成文档过程已经结束时调用——生成javadoc或groovydoc时
SetClasspath rootLoader 在classpath初始化时调用以便插件可以通过 rootLoader.addURL(...)来扩大classpath。注意这种扩大classpath是在事件脚本被加载 之后进行的,因此你不能使用这种方式来加载你的事件脚本需要导入的类,即使你可以通过名称来加载类。
PackagingEnd none 当打包结束时调用(这个调用是在Jetty服务器被启动之前并在web.xml文件被生成之后)
ConfigureJetty Jetty Server object 在Jetty web服务器的配置被初始化之后调用。

4.4 自定义构建

Grails无疑是一个固执己见框架,并且它喜欢按照约定来进行配置,但这并不意味着你 不能 去配置它。在本章,我们将看到你可以如何去影响和修改标准的Grails构建。

默认

为了自定义一个构建,你首先需要知道你可以自定义些什么。Grails构建配置的核心就是 grails.util.BuildSettings 类,它包含了大量有用的信息。它控制了哪些类被编译、应用程序依赖什么以及其他类似的设置。

以下是一个配置选项和它们的默认值的集录:
属性 配置选项 默认值
grailsWorkDir grails.work.dir $USER_HOME/.grails/<grailsVersion>
projectWorkDir grails.project.work.dir <grailsWorkDir>/projects/<baseDirName>
classesDir grails.project.class.dir <projectWorkDir>/classes
testClassesDir grails.project.test.class.dir <projectWorkDir>/test-classes
testReportsDir grails.project.test.reports.dir <projectWorkDir>/test/reports
resourcesDir grails.project.resource.dir <projectWorkDir>/resources
projectPluginsDir grails.plugins.dir <projectWorkDir>/plugins
globalPluginsDir grails.global.plugins.dir <grailsWorkDir>/global-plugins

BuildSettings 类也有一些其他属性,但是它们应该被只读处理:
属性 描述
baseDir 项目的位置。
userHome 用户的主目录。
grailsHome 正在使用的Grails的安装位置(也许为null)。
grailsVersion 被项目使用的Grails的版本。
grailsEnv 当前的Grails环境。
compileDependencies 编译时项目依赖的 文件 实例列表。
testDependencies 测试时项目依赖的 文件 实例列表。
runtimeDependencies 运行时项目依赖的 文件 实例列表。

当然,如果你不能获得这些属性那么它们并没有多好。幸运的是这很容易实现:通过grailsSettings脚本变量可以得到一个 BuildSettings 实例用于你的脚本。你也可以在你的代码中通过使用 grails.util.BuildSettingsHolder 类来访问它,但是并不推荐这样做。

覆盖默认值

所有在第一个表中的属性都可以被一个系统属性或配置选项所覆盖——简单地使用“config option”名称。例如,要改变项目工作目录,你可以运行这个命令:

grails -Dgrails.project.work.dir=work compile
或者将这个选项添加到你的 grails-app/conf/BuildConfig.groovy 文件中:
grails.project.work.dir = "work"
注意默认值带有许多它们依赖的属性值,因此像这样设置项目工作目录也将迁移编译好的类、测试类、资源和插件。

如果你同时使用系统属性和配置选项将发生什么?当然是系统属性被采用了,因为它优先于 BuildConfig.groovy 文件,而后者优先于默认值。

BuildConfig.groovy 文件是 grails-app/conf/Config.groovy 的姐妹文件,——过去包含的选项仅仅影响构建,但是之后包含的就影响正在运行的应用程序了。这并不局限于第一个表中的选项:你会发现构建配置选项在文档中到处都是,比如其中一些就用来指定内嵌的servlet容器应该运行在哪个端口上或者决定哪些文件应该被打包到WAR文件中。

可用的构建设置

名称 描述
grails.server.port.http 指定内嵌的servlet容器应该运行的端口(“run-app”和“run-war”命令使用)。整型。
grails.server.port.https 指定内嵌的servlet容器用于HTTPS的运行端口(“run-app https”和“run-war https”)。整型。
grails.config.base.webXml 指定用于应用程序的自定义web.xml文件的路径(取代使用web.xml模板)。
grails.compiler.dependencies 将额外的依赖添加到编译器classpath的传统方式。设置它到一个包含“fileset()”入口的闭包。
grails.testing.patterns 一个Ant路径格式的列表,允许你控制哪些文件可以被包含在测试中。这些格式不应该包括测试用例后缀,它们将在下一个属性中设置。
grails.testing.nameSuffix 默认的,测试类都假定有一个“Tests”的后缀。你可以设置这个选项来改变它为你想要的任何内容。例如:另一个公共后缀是“Test”。
grails.war.destFile 一个包含了生成的WAR文件的文件路径的字符串,除了它的全名意外(包括扩展名)。例如,“target/my-app.war”。
grails.war.dependencies 一个包含“fileset()”入口的闭包,它允许你完全控制什么内容可以被放入WAR文件的“WEB-INF/lib”目录中。
grails.war.copyToWebApp 一个包含“fileset()”入口的闭包,它允许你完全控制什么内容可以被放入WAR文件的根目录中。它覆盖了包含“web-app”目录下所有内容的那种默认习惯。
grails.war.resources 一个闭包,它的第一个参数作为分段目录的位置。你可以使用任何Ant任务来做你想做的任何事。通常这用来在目录被打包成WAR之前从分段目录中删除文件。

4.5 Ant和Maven

如果你的团队或公司的所有其他项目都在使用像Ant或Maven这样的标准的构建工具进行构建的,当你使用Grails命令行来构建你的应用程序时你可能成为害群之马。幸运的是,今天你可以很容易的将Grails构建系统集成到正在使用的主要构建工具中(嗯,至少是在Java项目中使用的那种构建工具)。

Ant集成

当你通过 create-app 命令来创建一个Grails应用程序时,Grails会自动为你创建一个 Apache Ant 工具使用的build.xml文件,这个文件包含了下列的任务:

这些任务都可以被Ant运行,例如:

ant war

为了实现依赖管理,构建文件已经被全面改进为使用 Apache Ivy,这意味着它可以自动下载所有需要的Grails JAR文件和其他以来的文件。你甚至不必在本地安装Grails就可以使用它了!这对于需要使用像CruiseControlHudson这样的持续集成系统进行自动构建时特别有用。

这里使用了Grails的Ant task来对现有的Grails构建系统进行钩子操作。这个任务允许你运行任何可用的Grails脚本,不只是由生成的构建文件所使用的那些。要使用某个任务,你必须先声明它:

<taskdef name="grailsTask"
         classname="grails.ant.GrailsTask"
         classpathref="grails.classpath"/>

这也引出了另外的问题:“grails.classpath”中应该是什么内容?这个任务本身是在“grails-bootstrap”这个JAR工件中的,因此至少这个工件需要在classpath中。同时也应该包含“groovy-all”这个JAR。对于定义这个任务,你只需要使用这个!下表列出了可用的属性:
属性 描述 是否必填
home 构建时需要用到的Grails安装目录的位置。 除非classpath被指定否则必填。
classpathref 载入Grails的Classpath。必须包含“grails-bootstrap”工件并且应该包含“grails-scripts”。 除非home被设置或者你使用classpath元素否则必填。
script 要运行的Grails脚本的名称,例如:“TestApp”。 必填。
args 要加入脚本中的参数,例如:“-unit -xml”。 不是必填。默认为“”。
environment 运行脚本时的Grails环境。 不是必填。默认为脚本的default。
includeRuntimeClasspath 高级设置:如果设为true则将应用程序的运行时classpath添加到构建classpath中。 不是必填。默认为true。

这个任务也支持下列内嵌元素,这些全都是标准的Ant路径结构:

要如何填写这些路径信息完全取决于你。如果你正在使用 home 属性并且把你自己的依赖内容放在了 lib 目录中,那么你不需要使用以上任何一个路径。如果想看看使用它们的例子,那么就查看为一个新应用而生成的Ant构建文件吧。

Maven集成

从1.1版本起,Grails通过一个Maven插件提供了与 Maven 2 的集成。当前作为基础的Maven插件,特别是由 Octo 创建的这个版本是非常有效的,它做得非常出色。

准备

为了使用这个新的插件,你只需要安装和设置Maven 2。这是因为 你不再需要单独的安装Grails为了使用Maven!

Grails集成Maven 2已经针对Maven 2.0.9及以上版本进行了设计和测试。它将无法工作在更早期的版本中。

为了让你的生活更轻松,我们强烈推荐你添加一个用于Grails的插件组到Maven的设置文件中($USER_HOME/.m2/settings.xml):

<settings><pluginGroups>
    <pluginGroup>org.grails</pluginGroup>
  </pluginGroups>
</settings>

另外,如果你已经有用于Grails设置的Octo Maven工具,那么你需要删除 com.octo.mtg 插件组。

创建一个 Grails Maven 项目

要简单地创建一个支持Maven的Grails项目只要运行下边的命令:

mvn archetype:generate -DarchetypeGroupId=org.grails \
    -DarchetypeArtifactId=grails-maven-archetype \
    -DarchetypeVersion=1.0-SNAPSHOT \
    -DarchetypeRepository=http://snapshots.repository.codehaus.org \
    -DgroupId=example -DartifactId=my-app

无论你想为你的应用选择哪个group ID和artifact ID,一切内容格式都必须像上面写的那样。这将创建一个新的Maven项目以及一个POM文件和一系列其他文件。你不会看到有什么是像一个Grails应用。因此,下一步就要创建一个你要使用的项目结构了:

cd my-app
mvn initialize

现在你已经有一个可以使用的Grails应用了。插件已经集成到了标准的构建周期,因此你可以使用标准的Maven语法来构建和打包你的应用程序了: mvn cleanmvn compilemvn testmvn package

你也可以利用许多已经被包装成Maven目标的Grails命令:

给现有项目加入Maven支持

创建一个全新的项目当然是一个很好的途径,但如果已经有一个项目了该怎么办呢?你应该不会愿意先创建一个新项目然后再把旧项目的内容拷贝进去的。解决方法是使用下列命令为现有项目创建一个POM文件:

mvn grails:create-pom -DgroupId=com.mycompany
当这个命令完成时,你就可以立即使用标准的语法了,如 mvn package 。需要注意的是当创建POM文件时你必须指定一个group ID。

添加Grails命令到 phase 中

标准的POM文件被创建是为了让Grails将合适的核心Grails命令附加到它们对应的构建语法上,因此“compile”对应“compile”语法,“war”对应“package”语法。当你想要将一个插件的命令附加到一个特定的phase上时,这可能没有什么帮助。典型的例子是功能测试。你如何确保你的功能测试(无论正在使用你决定的哪个插件)是使用“integration-test” phase来运行的?

恐怕不是:所有事情都是可能的。在这个例子中,你可以使用额外的“execution”块来将命令联合到一个 phase 上:

<plugin>
        <groupId>org.grails</groupId>
        <artifactId>grails-maven-plugin</artifactId>
        <version>1.0-SNAPSHOT</version>
        <extensions>true</extensions>
        <executions>
          <execution>
            <goals></goals>
          </execution>
          <!-- 添加 "functional-tests" 命令到 "integration-test" phase -->
          <execution>
            <id>functional-tests</id>
            <phase>integration-test</phase>
            <goals>
              <goal>exec</goal>
            </goals>
            <configuration>
              <command>functional-tests</command>
            </configuration>
          </execution>
        </executions>
      </plugin>

这也展示了 grails:exec 目标,它可以用来运行任何Grails命令。简单的将命令的名字作为 command 系统特性,还可以通过 args 特性来选择性地指定参数:

mvn grails:exec -Dcommand=create-webtest -Dargs=Book

Show details